# -*- codeing = utf-8 -*-
# @Time  :2021/6/28 22:03
# @Author: wangyuhan
# @File  :BaiduScholar.py
# @Software: PyCharm

'''
一、
    检索通式：
    百度学术网址：https://xueshu.baidu.com
    高级检索：检索词+作者+机构名称
    https://xueshu.baidu.com/s?wd=关键词+author作者+affs%3A%28机构名称%29
二、
    匹配规则：使用bs4.BeautifulSoup, lxml库进行解析
    匹配href=re.compile('^//xueshu.baidu.com')
三、
    分析页面：(需要注意：可能有些资料是图书，没有摘要，则跳过)
    作者：re.compile('"{\'button_tp\':\'author\'}">(.*?)</a>')
    摘要：re.compile('<p class="abstract" data-sign="">(.*?)</p>')
    关键词：re.compile('target="_blank" class="">(.*?)</a></span>')
    DOI: re.compile('data-click="{\'button_tp\':\'doi\'}">\s+(.*?)\s+</p>')
    被引量：re.compile('"{\'button_tp\':\'sc_cited\'}">\s+(\d+)\s+</a>')
    年份：re.compile('<p class="kw_main" data-click="{\'button_tp\':\'year\'}">\s+(\d+)\s+</p>')
    标题：re.compile('"{\'act_block\':\'main\',\'button_tp\':\'title\'}"\s+>\s+(\S+)\s+</a>')
四、
    不爬取图书，若匹配不到摘要，则放弃该文献
爬虫思路：
1、爬取网页
2、解析数据
3、保存数据

'''
from bs4 import BeautifulSoup
import re
import requests
import os
import pandas as pd
import sys
import random
import time
# 导入自定义模块（GUI组件）
import WindowShow
import DataVis

class Crawler_Paper():
    '''百度学术爬虫类'''
    def __init__(self):
        self.base_url = 'https://xueshu.baidu.com'
        self.header = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"}
        self.paper_num = 1
        self.data = pd.DataFrame()    # 用于保存文献信息

    def input_message(self, url):
        '''
        输入检索信息，并且修改url
        :param url: 待爬网址
        :return x_url: 返回修改后的url，进行了关键词、作者和机构检索
        '''
        try:
            s = input('是否输入检索词(y/n，不区分大小写)>')
            if s == 'y' or s =='Y':
                keyword = input('检索词>')
                x_url = url + '/s?wd=' + keyword
            elif s !='n' and s != 'N':
                print('Input Error!')
                sys.exit('Goodbye!')

            a = input('是否输入作者(y/n，不区分大小写)>')
            if a == 'y' or a == 'Y':
                author = input('作者>')
                x_url = x_url + 'author' + author
            elif a !='n' and a != 'N':
                print('Input Error!')
                sys.exit('Goodbye!')

            af = input('是否输入机构(y/n，不区分大小写)>')
            if af == 'y' or af =='Y':
                affs = input('机构>')
                x_url = x_url + 'affs%3A%28' + affs + '%29'
            elif af != 'n' and af != 'N':
                print('Input Error!')
                sys.exit('Goodbye!')
            # 返回修改后的url
            return x_url
        # 增强程序的容错率，若没有输入，则退出程序
        except:
            print('No input!')
            sys.exit('Goodbye!')

    def get_page(self, url, params=None):
        '''
        获取网页信息函数，发送请求，获取响应内容
        :param url: 需要进入的网址
        :return req.text: 返回解码后的网页信息
        '''

        # 休眠，避免被对方反爬到
        time.sleep(random.randint(1,5))
        req = requests.get(url, headers=self.header, params=params)

        # 返回解码后的内容：requests.get().text
        # 返回字节：requests.get().content
        return req.text

    def analyze_paper(self, url):
        '''
        对每一个文章的网页进行检索，获取信息
        :param url: 需要检索的文章的url
        :return: msg: 包含文章的标题、作者、DOI、摘要、被引量、年份、关键词信息，若返回None,则表示不为期刊论文，跳过该文献
        '''
        content = self.get_page(url)
        # 匹配结果均为一个列表，关键词、作者、需要保留为List，其余需要转换为str
        # 关键词
        keyword = re.findall('target="_blank" class="">(.*?)</a></span>', content)
        if not keyword:
            print('不为论文...')   # 如果检索的不是期刊论文，则跳过
            return None
        msg = dict(keyword=keyword)
        # 文章标题
        title = re.findall('"{\'act_block\':\'main\',\'button_tp\':\'title\'}"\s+>\s+(\S+)\s+</a>', content)
        # 作者
        author = re.findall('"{\'button_tp\':\'author\'}">(.*?)</a>', content)
        # 摘要
        abstract = re.findall('<p class="abstract" data-sign="">(.*?)</p>', content)
        # DOI
        DOI = re.findall('data-click="{\'button_tp\':\'doi\'}">\s+(.*?)\s+</p>', content)
        # 被引量
        f = re.findall('"{\'button_tp\':\'sc_cited\'}">\s+(\d+)\s+</a>', content)
        # 年份
        pub_time = re.findall('<p class="kw_main" data-click="{\'button_tp\':\'year\'}">\s+(\d+)\s+</p>', content)
        # 更新字典
        # 使用try处理异常，可能有些页面会有信息缺失
        print(author)
        try:
            msg.update({'title': title[0],
                        'author': author,
                        'abstract': abstract[0],
                        'DOI': DOI[0],
                        'f': f[0],
                        'time': pub_time[0]
                        })
        except IndexError:
            Ti = None if title==[] else title[0]
            Au = None if author==[] else author[0]
            Ab = None if abstract==[] else abstract[0]
            Doi = None if DOI==[] else DOI[0]
            fac = None if f==[] else f[0]
            Pt = None if pub_time==[] else pub_time[0]
            msg.update({'title': Ti,
                        'author': Au,
                        'abstract': Ab,
                        'DOI': Doi,
                        'f': fac,
                        'time': Pt
                        })
        return msg

        # 核心函数
    def analyze_page(self, content):
        '''
        解析网页信息
        :param content: 获取的网页信息（解码后的）
        '''
        # 获取每一个文章的链接
        soup = BeautifulSoup(markup=content, features='lxml')
        urls_list = soup.find_all('a', href=re.compile('^//xueshu.baidu.com/usercenter/paper'))
        # 对该页面所有文章链接进行迭代
        for url in urls_list:
            message = self.analyze_paper('https:'+url['href'])  # 获取文章页面的信息
            if message:
                '''保存数据'''
                print(f'第{self.paper_num}篇文章爬取中')
                self.save_data(message)     # 保存文献信息
                self.paper_num += 1
            else:
                continue
        # 进行下一页爬取，判断是否还存在下一页的链接
        next_urls = re.findall('<a href="(.*?)" class="n" style="margin-right', content)
        # 若存在下一页内容，则继续爬取
        if next_urls:
            next_url = self.base_url + next_urls[0]
            content = self.get_page(next_url)
            self.analyze_page(content)
        else:
            print('爬虫结束...')

    def save_data(self, msg):
        '''
        保存数据，包括文章标题、作者、关键词、被引量、摘要、年份、DOI
        :param msg: 有七个键值对的字典
        '''
        MSG = {'title':msg['title'],
               'author':str(['author']),
               'keyword':str(['keyword']),
               'f':msg['f'],
               'time':msg['time'],
               'DOI':msg['DOI'],
               'abstract':msg['abstract']
               } # 重新组织字典内顺序

        self.data = self.data.append(pd.DataFrame([MSG]))

    def start(self):
        '''执行爬虫'''
        print('爬虫开始...')
        # 创建存储信息的文件夹
        try:
            os.makedirs('./BaiduScholar/')
        except FileExistsError:  # 如果文件夹已经存在，则跳过
            pass
        new_url = self.input_message(self.base_url)
        content = self.get_page(url=new_url)
        self.analyze_page(content)
        self.data.to_excel('./BaiduScholar/BaiduScholar.xlsx', index=False)

if __name__=='__main__':
    '''调用自定义爬虫类：Crawler_Paper'''
    crawler = Crawler_Paper()
    crawler.start()
    '''对爬取数据进行窗口可视化'''
    WindowShow.MainForm()     # 窗口显示
    DataVis.main()            # 可视化数据


